/*
 
Copyright Eric C. McCreath 2012  <ericm@cs.anu.edu.au>

This file is part of the clapper-webcam-recorder project.

clapper-webcam-recorder is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.

clapper-webcam-recorder is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with clapper-webcam-recorder.  If not, see <http://www.gnu.org/licenses/>.

 */



#include "util.h"

 
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <stdio.h>
#include <time.h>

#include "webcam.h" 




unsigned int     n_buffers       = 0;
struct buffer *  buffers         = NULL;

int webfd; // the file discriptor of the webcam

// send a ioctl message to the webcam
int ctl_webcam(int request, void *arg)
{
        int r;
        do r = ioctl (webfd, request, arg);
        while (-1 == r && EINTR == errno);
        return r;
}


// open the webcam
void open_webcam(char *name)
{
    struct stat st; 
    if (-1 == stat(name, &st)) errno2_exit("Cannot identify '%s' ", name);
    if (!S_ISCHR(st.st_mode)) s2_exit("%s is no device\n", name);
    if (-1 == (webfd = open(name, O_RDWR | O_NONBLOCK, 0))) errno2_exit("Cannot open '%s' ", name);
}

void init_webcam()
{
    struct v4l2_capability cap;
    struct v4l2_format fmt;
    unsigned int min;

    if (-1 == ctl_webcam(VIDIOC_QUERYCAP, &cap)) {
        if (EINVAL == errno) {
            fprintf (stderr, "webcam is no V4L2 device\n");
            exit(EXIT_FAILURE);
        } else {
            errno_exit ("VIDIOC_QUERYCAP");
        }
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        fprintf (stderr, "webcam is no video capture device\n");
        exit (EXIT_FAILURE);
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        fprintf (stderr, "webcam does not support streaming i/o\n");
        exit (EXIT_FAILURE);
    }


    CLEAR (fmt);

    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = 640; 
    fmt.fmt.pix.height      = 480;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

    if (-1 == ctl_webcam (VIDIOC_S_FMT, &fmt))   // this operation requires privileges
        errno_exit ("VIDIOC_S_FMT");

    min = fmt.fmt.pix.width * 2;
    if (fmt.fmt.pix.bytesperline < min)
        fmt.fmt.pix.bytesperline = min;
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if (fmt.fmt.pix.sizeimage < min)
        fmt.fmt.pix.sizeimage = min;

    // setup the buffers
    struct v4l2_requestbuffers req;
    CLEAR (req);

    req.count = 2;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (-1 == ctl_webcam (VIDIOC_REQBUFS, &req)) {
        if (EINVAL == errno) s1_exit("webcam device not support memory mapping\n");
        else errno_exit ("VIDIOC_REQBUFS");
    }

    if (req.count < 2) {
        s1_exit("Insufficient buffer memory on webcam device\n");
    }

    if (!(buffers = calloc (req.count, sizeof (*buffers)))) 
        s1_exit("Out of memory - allocating buffers\n");
              
    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
                struct v4l2_buffer buf;

                CLEAR (buf);

                buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory      = V4L2_MEMORY_MMAP;
                buf.index       = n_buffers;

                if (-1 == ctl_webcam (VIDIOC_QUERYBUF, &buf))
                        errno_exit ("VIDIOC_QUERYBUF");

                buffers[n_buffers].length = buf.length;
                 if (MAP_FAILED == (buffers[n_buffers].start =
                        mmap (NULL,
                              buf.length,
                              PROT_READ | PROT_WRITE,
                              MAP_SHARED,
                              webfd, buf.m.offset))) errno_exit ("mmap");
        }
}


void
stop_webcam(void) {
    enum v4l2_buf_type type;
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == ctl_webcam (VIDIOC_STREAMOFF, &type))
        errno_exit ("VIDIOC_STREAMOFF");
}

void
start_webcam(void)
{
    unsigned int i;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers; ++i) {
        struct v4l2_buffer buf;

        CLEAR (buf);
        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory      = V4L2_MEMORY_MMAP;
        buf.index       = i;
        if (-1 == ctl_webcam (VIDIOC_QBUF, &buf)) errno_exit ("VIDIOC_QBUF - start_webcam");
    }
        
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == ctl_webcam (VIDIOC_STREAMON, &type)) errno_exit ("VIDIOC_STREAMON");
}

struct timespec starttime; 

void
setstarttime() {
     if (clock_gettime(CLOCK_MONOTONIC_RAW,&starttime)) s1_exit("problem getting the raw clock time");
}

double 
timefromstart() {
    struct timespec currenttime; 
    if (clock_gettime(CLOCK_MONOTONIC_RAW,&currenttime)) s1_exit("problem getting the raw clock time (currenttime)");
    return (1.0 * (currenttime.tv_sec - starttime.tv_sec)) - ((1000000000L - starttime.tv_nsec)/1000000000.0) + (currenttime.tv_nsec/1000000000.0);

}

// returns the time the frame was caputred in seconds after the start timer was set
double capturetime(struct v4l2_buffer * buf) {
   return (1.0 * (buf->timestamp.tv_sec - starttime.tv_sec)) - ((1000000000L - starttime.tv_nsec)/1000000000.0) + (buf->timestamp.tv_usec/1000000.0);  
}

int getbuffer_webcam(struct v4l2_buffer * buf) {
   // wait for the buffer to be ready
    fd_set fds;
    struct timeval tv;
    int r;

    FD_ZERO (&fds);
    FD_SET (webfd, &fds);

    tv.tv_sec = 4;
    tv.tv_usec = 0;

    do {
        r = select (webfd + 1, &fds, NULL, NULL, &tv);
    } while (r == -1 && EINTR == errno);
    if (r == 0) errno_exit ("select timeout");
    if (r == -1)  errno_exit ("select problem");
            

    // then grab it
    CLEAR (*buf);
    buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf->memory = V4L2_MEMORY_MMAP;

    if (-1 == ctl_webcam ( VIDIOC_DQBUF, buf)) {
        switch (errno) {
            case EAGAIN:
                return -1;

            case EIO:
               
            default:
                errno_exit ("VIDIOC_DQBUF");
            }
        }
     return buf->index;

}

void releasebuffer_webcam(struct v4l2_buffer *buf) {

    if (-1 == ctl_webcam (VIDIOC_QBUF, buf))
            errno_exit ("VIDIOC_QBUF - releasebuffer_webcam");
}

// close the webcam
void close_webcam()
{
    unsigned int i;
    
    for (i = 0; i < n_buffers; ++i)
        if (-1 == munmap (buffers[i].start, buffers[i].length))
            errno_exit ("munmap problem");
        
    free (buffers);
   
    if (close(webfd) == -1)errno_exit ("problem closing");
    webfd = -1;
}


